package qsbk.app.play.websocket;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;

import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;

import com.google.gson.Gson;

import qsbk.app.play.common.Constants;
import qsbk.app.play.model.Room;
import qsbk.app.play.websocket.model.BaseMessage;
import qsbk.app.play.websocket.model.BaseRoomMessage;
import qsbk.app.play.websocket.model.GameOverMessage;
import qsbk.app.play.websocket.model.GameStartMessage;
import qsbk.app.play.websocket.model.MatchProgressMessage;
import qsbk.app.play.websocket.model.PerformMessage;
import qsbk.app.play.websocket.model.PerformTopicAnswerMessage;
import qsbk.app.play.websocket.model.PerformTopicAnswerResultMessage;
import qsbk.app.play.websocket.model.PerformTopicMessage;
import qsbk.app.play.websocket.model.PerformTopicSelectedMessage;

//该注解用来指定一个URI，客户端可以通过这个URI来连接到WebSocket。类似Servlet的注解mapping。无需在web.xml中配置。
@ServerEndpoint("/websocket")
public class GameWebSocket {
	
	// 游戏每轮表演时间
	private static final int TOPIC_ROUND_DURATION = 10;

	// 静态变量，用来记录当前在线连接数。应该把它设计成线程安全的。
	private static AtomicInteger onlineCount = new AtomicInteger(0);

	// 额定玩家数量
	private static int limitCount = 4;

	// JSON解析
	private static Gson gson = new Gson();

	// concurrent包的线程安全Set，用来存放每个客户端对应的MyWebSocket对象。若要实现服务端与单一客户端通信的话，可以使用Map来存放，其中Key可以为用户标识
	private static LinkedBlockingQueue<GameWebSocket> mWaitingQueue = new LinkedBlockingQueue<GameWebSocket>();

	// 与某个客户端的连接会话，需要通过它来给客户端发送数据
	private Session session;

	// 正在游戏的队列
	private static Map<Integer, Room> mGamingQueue = new HashMap<>();
	
	// 绑定游戏房间的roomId
	private int attachedRoomId;
	// 自增长的房间ID
	private static int autoIncrementRoomId = 1000;

	/**
	 * 连接建立成功调用的方法
	 * 
	 * @param session
	 *            可选的参数。session为与某个客户端的连接会话，需要通过它来给客户端发送数据
	 */
	@OnOpen
	public void onOpen(Session session) {
		this.session = session;

		addOnlineCount(); // 在线数加1
		System.out.println("有新连接加入！当前在线人数为" + getOnlineCount());

		String uid = getUid();
		for (Room room : mGamingQueue.values()) {
			if (room.hasUser(uid)) {
				room.onOpen(this);
				System.out.println("用户重连了，" + uid + "[房间" + room.getRoomId() + "]");
				return;// 断网重连用户
			}
		}

		mWaitingQueue.add(this); // 加入set中
		int waitingUserSize = mWaitingQueue.size();
		if (waitingUserSize <= limitCount) {
			MatchProgressMessage matchProgressMsg = new MatchProgressMessage(waitingUserSize, limitCount);
			broadcast(matchProgressMsg);
		}
		if (waitingUserSize == limitCount) {
			autoIncrementRoomId++;
			int roomId = autoIncrementRoomId;

			List<GameWebSocket> websockets = new ArrayList<>(limitCount);
			for (GameWebSocket websocket : mWaitingQueue) {
				if (websockets.size() < limitCount) {
					mWaitingQueue.remove(websocket);
					websocket.attachedRoomId = roomId;
					websockets.add(websocket);
				} else {
					break;
				}
			}

			Room room = new Room();
			room.setRoomId(roomId);
			room.setWebsockets(websockets);
			mGamingQueue.put(roomId, room);

			GameStartMessage startMsg = new GameStartMessage(roomId);
			room.broadcast(startMsg);

			System.out.println("游戏开始 " + roomId);

			PerformMessage performMsg = new PerformMessage();
			performMsg.who = room.getNextPerform();
			performMsg.count = TOPIC_ROUND_DURATION;
			performMsg.topics = room.getTopics();// TODO 仅表演者返回
			room.broadcast(performMsg);

			System.out.println("开始选择题目进行游戏 " + roomId);

			doCountDown(roomId);
		}
	}

	public String getUid() {
		String uid = null;
		Map<String, List<String>> paramsMap = session.getRequestParameterMap();
		if (paramsMap != null) {
			List<String> uids = paramsMap.get("uid");
			if (uids != null && uids.size() > 0) {
				uid = uids.get(0);
			}
		}

		if (uid == null || uid.equals("")) {
			uid = session.getId();
		}

		return uid;
	}

	private void doCountDown(final int roomId) {
		System.out.println("Hello 定时器任务开始执行!!! " + roomId);
		TimerTask task = new TimerTask() {
			@Override
			public void run() {
				Room room = mGamingQueue.get(roomId);

				// task to run goes here
				System.out.println("Hello 定时器任务执行完毕!!! " + roomId);
				String nextPerform = room.getNextPerform();
				if (nextPerform != null && !nextPerform.equals("")) {
					PerformMessage performMsg = new PerformMessage();
					performMsg.who = nextPerform;
					performMsg.count = TOPIC_ROUND_DURATION;
					performMsg.topics = room.getTopics();// TODO 仅表演者返回
					room.broadcast(performMsg);

					doCountDown(roomId);
				} else {
					// 游戏结束
					GameOverMessage gameOverMsg = new GameOverMessage();
					room.broadcast(gameOverMsg);

					room.close();
					mGamingQueue.remove(room);
					room = null;
				}
			}
		};
		Timer timer = new Timer();
		long delay = TOPIC_ROUND_DURATION * 1000;
		// schedules the task to be run in an interval
		timer.schedule(task, delay);
	}

	/**
	 * 连接关闭调用的方法
	 */
	@OnClose
	public void onClose() {
		mWaitingQueue.remove(this); // 从set中删除
		if (attachedRoomId > 0) {
			Room room = mGamingQueue.get(attachedRoomId);
			room.onClose(this);
		}
		subOnlineCount(); // 在线数减1
		System.out.println("有一连接关闭！当前在线人数为" + getOnlineCount());
	}

	/**
	 * 收到客户端消息后调用的方法
	 */
	@OnMessage
	public void onMessage(String message, Session session) {
		System.out.println("来自客户端的消息:" + message);

		try {
			BaseRoomMessage baseMsg = gson.fromJson(message, BaseRoomMessage.class);
			if (baseMsg != null) {
				Room room = mGamingQueue.get(baseMsg.roomId);
				BaseMessage forwardMsg = null;
				switch (baseMsg.type) {
				case Constants.MessageType.PERFORM_TOPIC_SELECTED:
					PerformTopicSelectedMessage topicMsg = gson.fromJson(message, PerformTopicSelectedMessage.class);
					String topic = topicMsg.topic;
					room.setPerformTopic(topic);

					PerformTopicMessage performTopicMsg = new PerformTopicMessage();
					performTopicMsg.wordCount = topic.length();
					performTopicMsg.words = room.generateGuessWords(topic);
					forwardMsg = performTopicMsg;
					break;
				case Constants.MessageType.PERFORM_TOPIC_ANSWER:
					PerformTopicAnswerMessage answerMsg = gson.fromJson(message, PerformTopicAnswerMessage.class);
					String answer = answerMsg.answer;

					PerformTopicAnswerResultMessage answerResultMsg = new PerformTopicAnswerResultMessage();
					answerResultMsg.result = room.getPerformTopic().equals(answer);
					answerResultMsg.answer = answer;
					forwardMsg = answerResultMsg;
					break;
				}
				if (forwardMsg != null) {
					room.broadcast(forwardMsg);// 消息经过处理后转发/广播给所有用户
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public void broadcast(Object obj) {
		broadcast(gson.toJson(obj));
	}

	public void broadcast(String message) {
		// 群发消息
		for (GameWebSocket item : mWaitingQueue) {
			try {
				item.sendMessage(message);
			} catch (IOException e) {
				e.printStackTrace();
				continue;
			}
		}
	}

	/**
	 * 发生错误时调用
	 */
	@OnError
	public void onError(Session session, Throwable error) {
		System.out.println("房间: " + attachedRoomId + " " + getUid() + " 发生错误");
		error.printStackTrace();
	}

	/**
	 * 这个方法与上面几个方法不一样。没有用注解，是根据自己需要添加的方法。
	 * 
	 * @param message
	 * @throws IOException
	 */
	public void sendMessage(String message) throws IOException {
		this.session.getBasicRemote().sendText(message);
		if (attachedRoomId > 0) {
			System.out.println("房间: " + attachedRoomId + " " + getUid() + " 发送消息：" + message);
		} else {
			System.out.println(getUid() + " 发送消息：" + message);
		}
		// this.session.getAsyncRemote().sendText(message);
	}

	public static synchronized int getOnlineCount() {
		return onlineCount.get();
	}

	public static synchronized void addOnlineCount() {
		onlineCount.incrementAndGet();
	}

	public static synchronized void subOnlineCount() {
		onlineCount.decrementAndGet();
	}

	public Session getSession() {
		return session;
	}

}